iT邦幫忙

2026 iThome 鐵人賽

DAY 26
0
AI 自動化

用 LINE Bot 和 Agent Skill 自動化整理筆記與 IG 發文系列 第 26 篇

Day 26|用 GitHub public repo 當圖床,並確認網址真的打得開

  • 分享至 

  • xImage
  •  

昨天講安全設計時提到,upload_images.py 傳完圖會對每個網址發 HEAD 請求,有一張抓不到就停下來,說明天細講。今天就講這支腳本:圖卡算完之後,怎麼變成 IG 抓得到的網址。

為什麼需要圖床

IG 的 Content Publishing API 沒有上傳檔案的端點,建立貼文只吃 image_url。我手上的圖卡是本機資料夾裡的 PNG,API 看不到,得先放到一個網址上,由 Meta 的伺服器自己去抓。

這件事有個容易忽略的重點:是 Meta 的伺服器去抓,不是我的電腦。references/ig-api.md 把會失敗的情況列得很清楚:localhost 不行、內網 IP 不行、需要登入的網址不行、自簽憑證不行。Google Drive、Dropbox、OneDrive 的分享連結也不行,因為它們回的是一個 HTML 檢視頁,不是圖片本身。

換句話說,網址必須是任何人從網路上都打得開、而且打開直接就是圖。我在這篇會說的「網址打得開」,指的就是這個條件。

三種圖床,預設用 GitHub

腳本支援三種 image_host.method:github、scp(自己的 VPS 加 web server)、local(圖已經在某個有 web server 的本機目錄)。config.example.yaml 預設是 github,腳本開頭的註解形容它是「最省事」。

我自己讀下來覺得省事的地方在於不用另外養一台機器:開一個 public repo,再給一個 token,就有一個 raw.githubusercontent.com 的 HTTPS 網址可以用。scp 那條路要有 VPS、要設 web server,而且 public_base_url 必須是 HTTPS,腳本會檢查,不是就直接停。

設定檔裡 GitHub 這一段長這樣:

image_host:
  method: github
  repo: "your-name/ig-assets"    # owner/repo
  branch: "main"
  path: "posts"                  # repo 裡放圖的目錄
  token_env: "GITHUB_TOKEN"

token 照 Day 25 講的,放環境變數。腳本找不到時會提示去建一個 fine-grained token,Repository access 只選放圖的那個 repo,權限給 Contents: Read and write。這樣就算 token 外流,能動的也只有這一個專放圖片的 repo。

上傳其實是呼叫 Contents API

沒有用 git 指令,gh_upload 直接呼叫 GitHub 的 Contents API:把 PNG 轉成 base64,用 PUT 送到 repos/{repo}/contents/{path}/{檔名}。

有個小處理:PUT 之前會先 GET 一次同一個路徑,如果檔案已經存在,就把回來的 sha 帶進去,等於覆蓋。我重跑腳本的時候不會因為檔案已存在而報錯。

檔名是 日期-專案slug-card-01.png 這種格式,日期是跑腳本當天,slug 從 deck.json 的 project 欄位來,非英數字的字元換成減號。這個前綴後天清理的時候會用到。

上傳成功後,回傳的網址是這樣組的:

return f"https://raw.githubusercontent.com/{h['repo']}/{branch}/{gh_path(cfg, name)}"

傳完不算數,要驗證

這支腳本我覺得最值得講的是後半段。圖傳上去,GitHub 回 200,不代表 IG 抓得到。所以腳本接著對每個網址跑 verify():

req = Request(url, method="HEAD")
req.add_header("User-Agent", "facebookexternalhit/1.1")
with urlopen(req, timeout=20) as r:
    ctype = r.headers.get("Content-Type", "")
    if r.status == 200 and ctype.startswith("image/"):
        return True, ctype

要同時滿足兩件事:狀態碼 200,而且 Content-Type 是 image/ 開頭。只看 200 不夠,因為 Google Drive 那類連結就是回 200,內容卻是一頁 HTML。urlopen 預設會跟著 redirect 走,所以被導到登入頁的網址,最後拿到的會是 text/html,一樣過不了。

User-Agent 我設成 facebookexternalhit/1.1,這是 Meta 爬蟲自己用的名字,用意是盡量模擬 Meta 去抓的情境。這裡要老實說:這只是模擬,驗證是從我這台機器發出去的,不是從 Meta 的伺服器。如果哪個圖床會對特定來源做不同回應,我這邊驗過還是有可能不一樣。對 GitHub raw 來說這個風險很低,所以我接受。

重試與失敗時的行為

剛 push 完的檔案,raw 的 CDN 有時要幾秒才看得到。所以 verify() 最多試 5 次,每次失敗後等久一點,依序是 2、4、6、8 秒。遇到 HTTP 錯誤或連線錯誤才會重試,要是回 200 但 Content-Type 不是圖片,就直接判失敗,不再等。

只要有任何一張驗證失敗,腳本就呼叫 die() 結束,並且不會把網址寫進 deck.json。腳本也直接把最常見的三個原因印出來:

  • repo 是不是 private?raw 網址只對 public repo 有效
  • 分支名稱對不對(main 還是 master)
  • 剛 push 完 raw CDN 要幾秒才生效,等一下再跑一次

全部通過才會把網址陣列存成 deck.json 的 uploaded 欄位,印出「N 張都可達」,並提醒我再確認一次使用者說可以發,才跑 publish_ig.py。上一篇講的 publish_ig.py 開頭檢查「deck.json 有沒有上傳後的網址」,檢查的就是這個欄位。

我喜歡這個設計的原因是它把失敗提前了。ig-api.md 的排錯表裡,網址抓不到最後在 IG 這邊會變成一句 Media upload has failed,這句話幾乎看不出原因。在上傳這一步就驗,錯誤訊息是我自己寫的,會直接指出是哪個網址、什麼原因。

取捨

用 public repo 當圖床,代價是圖片在發布前就已經是公開的,知道網址的人都看得到。以這個用途來說,這些圖幾分鐘後就要發到 IG,我覺得影響不大,但這個前提是內容本來就打算公開。如果是還沒想公開的圖,就不該走這條路。

另外,圖會留在 repo 裡,而且是 git 歷史的一部分。腳本有 --cleanup 可以刪掉檔案,但刪除是新增一個 commit,歷史裡還是找得到。這個我沒有特別處理,因為圖本來就是公開的。

小結

圖床這一步要解決的問題,是讓 Meta 的伺服器抓得到圖。我用 GitHub public repo 加 raw 網址,上傳走 Contents API,傳完用 HEAD 請求確認狀態碼 200、內容是圖片,最多重試 5 次,沒過就不往下走。

明天講 IG API 本身:帳號要是什麼類型、token 怎麼取得、怎麼續期。


上一篇
Day 25|安全設計:教材是資料不是指令,以及兩道發布閘門
下一篇
Day 27|IG API:帳號類型、token 取得與續期
系列文
用 LINE Bot 和 Agent Skill 自動化整理筆記與 IG 發文 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言